﻿using DotNetty.Codecs;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Concurrent;
using System.Net;
namespace Athena.Transport.DotNetty
{
    using ChannelHandlerAdapers;
    using Codec;
    using Implementations;
    using Options;
    public class DotNettyTransportClientFactory : ITransportClientFactory, IDisposable
    {
        private readonly TransportOption _transportOption;
        private readonly IRpcMessageEncoder _rpcMessageEncoder;
        private readonly IRpcMessageDecoder _rpcMessageDecoder;
        private readonly ConcurrentDictionary<EndPoint, Lazy<ITransportClient>> _clientDictionary;

        private Bootstrap _bootstrap;
        private ILogger _logger;


        public static readonly AttributeKey<ITransportMessageSender> MessageSenderKey = AttributeKey<ITransportMessageSender>.ValueOf(typeof(DotNettyTransportClientFactory), nameof(ITransportMessageSender));

        public static readonly AttributeKey<ITransportMessageListener> MessageListenerKey = AttributeKey<ITransportMessageListener>.ValueOf(typeof(DotNettyTransportClientFactory), nameof(ITransportMessageListener));

        public static readonly AttributeKey<EndPoint> ClientEndPointKey = AttributeKey<EndPoint>.ValueOf(typeof(DotNettyTransportClientFactory), nameof(EndPoint));


        public DotNettyTransportClientFactory(TransportOption transportOption, IRpcMessageCodecFactory rpcMessageCodecFactory, ILogger<DotNettyTransportClientFactory> logger)
        {
            _transportOption = transportOption;
            _rpcMessageEncoder = rpcMessageCodecFactory.CreateEncoder();
            _rpcMessageDecoder = rpcMessageCodecFactory.CreateDecoder();
            _clientDictionary = new ConcurrentDictionary<EndPoint, Lazy<ITransportClient>>();
            _logger = logger;
            _bootstrap = CreateBootstrap();
            _bootstrap.Handler(new ActionChannelInitializer<ISocketChannel>(c =>
            {
                var pipeline = c.Pipeline;
                pipeline.AddLast(new LengthFieldPrepender(4));
                pipeline.AddLast(new LengthFieldBasedFrameDecoder(int.MaxValue, 0, 4, 0, 4));
                pipeline.AddLast(new TransportMessageChannelHandlerAdaper(_rpcMessageDecoder));
                pipeline.AddLast(new DefaultChannelHandlerAdaper(this));
            }));
        }

        public ITransportClient CreateClient(EndPoint endPoint)
        {
            if (_logger.IsEnabled(LogLevel.Debug))
            {
                _logger.LogError($"开始创建客户端 通讯地址：{endPoint}");
            }
            try
            {
                return _clientDictionary.GetOrAdd(endPoint, x => new Lazy<ITransportClient>(() =>
                {
                    var bootstrap = _bootstrap;
                    var channel = bootstrap.ConnectAsync(x).Result;

                    var messageSender = new DotNettyTransportMessageClientSender(_rpcMessageEncoder, channel);
                    channel.GetAttribute(MessageSenderKey).Set(messageSender);

                    var messageListener = new TransportMessageListener();
                    channel.GetAttribute(MessageListenerKey).Set(messageListener);

                    channel.GetAttribute(ClientEndPointKey).Set(x);

                    return new TransportClient(_transportOption, messageSender, messageListener, _logger);
                })).Value;
            }
            catch
            {
                _clientDictionary.TryRemove(endPoint, out var value);
                throw;
            }
        }

        public void DestoryClient(EndPoint endPoint)
        {
            _clientDictionary.TryRemove(endPoint, out var value);
        }

        public void Dispose()
        {
            foreach (var item in _clientDictionary)
            {
                (item.Value as IDisposable)?.Dispose();
            }
        }

        private Bootstrap CreateBootstrap()
        {
            var bootstrap = new Bootstrap();
            bootstrap.Channel<TcpSocketChannel>()
                .Option(ChannelOption.TcpNodelay, true)
                .Group(new MultithreadEventLoopGroup());

            return bootstrap;
        }
    }
}
